package com.director.core;

import com.director.core.annotation.DirectParam;
import com.director.core.annotation.DirectReturn;
import com.director.core.annotation.RemotingProviderConfig;
import org.apache.commons.lang.StringUtils;

import java.io.Serializable;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;

/**
 * Author: Simone Ricciardi
 * Date: 30-mag-2010
 * Time: 11.59.24
 */
public class DirectMethod implements Serializable {

   private DirectAction action;
   private String name;
   private boolean formHandler;
   private boolean namedParameters;
   private ReturnDataStrategy returnDataStrategy;
   private transient Method method;
   private transient List<DirectMethodParam> methodParams = new ArrayList<DirectMethodParam>();

   public DirectMethod(String name, Method method, DirectAction action) {
      this.name = name;
      this.method = method;
      this.action = action;
      this.initParams();
      this.extractFormHandler();
      this.extractStrategy();
   }

   private void extractFormHandler() {
      if(this.method.isAnnotationPresent(RemotingProviderConfig.class)) {
         RemotingProviderConfig config = this.method.getAnnotation(RemotingProviderConfig.class);
         this.formHandler = config.formHandler();
      }
   }

   private void extractStrategy() {
      Annotation[] annotations = this.method.getDeclaredAnnotations();
      for(Annotation annotation : annotations) {
         if (annotation.annotationType().isAnnotationPresent(DirectReturn.class)){
            DirectReturn directReturn = annotation.annotationType().getAnnotation(DirectReturn.class);
            Class<? extends ReturnDataStrategy> strategyClass = directReturn.strategy();
            try {
               this.returnDataStrategy = strategyClass.getConstructor(annotation.annotationType()).newInstance(annotation);
               return;
            } catch(Exception e) {
               throw new DirectException(e);
            }
         }
      }
   }

   private void initParams() {
      Class[] parameterTypes = this.method.getParameterTypes();
      for(int order = 0; order < parameterTypes.length; order++) {
         Class paramType = parameterTypes[order];
         DirectParam directParam = this.extractDirectParamAnnotation(order);
         DirectMethodParam methodParam = new DirectMethodParam(order, paramType, directParam);
         this.methodParams.add(methodParam);
      }
   }

   private DirectParam extractDirectParamAnnotation(int paramOrder) {
      Annotation[] annotations = this.method.getParameterAnnotations()[paramOrder];
      for(Annotation annotation : annotations) {
         if(annotation.annotationType().equals(DirectParam.class)) {
            if(StringUtils.isNotBlank(((DirectParam) annotation).name())) {
               this.namedParameters = true;
            }
            return (DirectParam) annotation;
         }
      }
      return null;
   }

   DirectTransactionResult invoke(DirectTransactionData data) {
      ExecutorAdapter executorAdapter = DirectContext.get().getConfiguration().getExecutorAdapter();
      return executorAdapter.execute(this.action, this, data);
   }

   public Object[] parseParameters(DirectTransactionData data) {
      List<Object> params = new ArrayList<Object>();
      for(DirectMethodParam methodParam : this.methodParams) {
         params.add(methodParam.parseFromTransactionData(data));
      }
      return params.size() > 0 ? params.toArray() : null;
   }

   public Object[] getDefaultParameterValues() {
      List<Object> params = new ArrayList<Object>();
      for(DirectMethodParam methodParam : this.methodParams) {
         params.add(methodParam.getDefaultValue());
      }
      return params.size() > 0 ? params.toArray() : null;
   }

   public String getName() {
      return this.name;
   }

   public String getMethodName() {
      return this.method.getName();
   }

   public int getLen() {
      return this.namedParameters ? 1 : this.methodParams.size();
   }

   public boolean isFormHandler() {
      return this.formHandler;
   }

   public Method getMethod() {
      return this.method;
   }

   public ReturnDataStrategy getReturnDataStrategy() {
      return this.returnDataStrategy;
   }

   public boolean hasReturnDataStrategy() {
      return this.returnDataStrategy != null;
   }

   @Override
   public boolean equals(Object o) {
      if(this == o) {
         return true;
      }
      if(!(o instanceof DirectMethod)) {
         return false;
      }

      DirectMethod that = (DirectMethod) o;
      return this.name.equals(that.name);
   }

   @Override
   public int hashCode() {
      return this.name.hashCode();
   }

   @Override
   public String toString() {
      final StringBuilder sb = new StringBuilder();
      sb.append("DirectMethod");
      sb.append("{name='").append(this.name).append('\'');
      sb.append(", len=").append(this.getLen());
      sb.append('}');
      return sb.toString();
   }
}
